package com.clp.protocol.iec104.client.ui;

import com.clp.protocol.iec104.apdu.Apdu;
import com.clp.protocol.iec104.apdu.IApdu;
import com.clp.protocol.iec104.apdu.SApdu;
import com.clp.protocol.iec104.apdu.UApdu;
import com.clp.protocol.iec104.client.async.MasterFuture;
import com.clp.protocol.iec104.client.async.MasterFutureListener;
import com.clp.protocol.iec104.client.async.sendapdu.SendTaSelectRes;
import com.clp.protocol.iec104.client.pipeline.state.control.MControlInfo;
import com.clp.protocol.iec104.client.pipeline.state.data.MDataInfo;
import com.clp.protocol.iec104.client.pipeline.state.data.MTaDataHandler;
import com.clp.protocol.iec104.client.pipeline.state.data.MTcDataHandler;
import com.clp.protocol.core.event.ListenableContainerEvent;
import com.clp.protocol.iec104.definition.Tc;
import com.clp.protocol.iec104.definition.TypeTag;
import com.clp.protocol.core.event.EventListener;
import com.clp.protocol.core.pdu.PduRecvCallback;
import com.clp.protocol.core.pdu.PduSendCallback;
import com.clp.protocol.core.pdu.nbytepdu.time2a.Time2a;
import com.clp.protocol.core.utils.time.DateFormatter;
import com.clp.protocol.iec104.apdu.apci.ICtrlArea;
import com.clp.protocol.iec104.apdu.asdu.IAsdu;
import com.clp.protocol.iec104.apdu.asdu.infoobj.InfoObj;
import com.clp.protocol.iec104.apdu.asdu.infoobj.infoelem.InfoElem;
import com.clp.protocol.iec104.apdu.asdu.infoobj.infoelem.TmInfoElem;
import com.clp.protocol.iec104.apdu.asdu.infoobj.infoelem.TsInfoElem;
import com.clp.protocol.iec104.apdu.asdu.infoobj.qua.Qua;
import com.clp.protocol.iec104.apdu.asdu.infoobj.qua.TmQua;
import com.clp.protocol.iec104.client.Iec104MasterManager;
import com.clp.protocol.iec104.client.Master;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 一个简单的监视面板
 */
public class MonitorPanel extends JPanel {
    private JPanel mainPanel;
    private JList<MasterLine> masterNameList;
    private final DefaultListModel<MasterLine> masterNameListModel = new DefaultListModel<>();
    private JTabbedPane rightTabbedPane;
    private JTextField cfgValRemoteHost;
    private JTextField cfgValRemotePort;
    private JTextField cfgValRtuAddr;
    private JTextField cfgValAutoStartDt;
    private JTextField cfgValTotalCall100PeriodSeconds;
    private JTextField cfgValTotalCall101PeriodSeconds;
    private JTextField cfgValT0;
    private JTextField cfgValT1;
    private JTextField cfgValT2;
    private JTextField cfgValT3;
    private JTextArea mainMonitorTextArea;
    private JTextField monitorIsStartedDt;
    private JTextField monitorIsInitEnd;
    private JTextField monitorIsTestFr;
    private JTextField monitorIsTotaCall100;
    private JTextField monitorIsTotalCall101;
    private JPanel leftPanel;
    private JPanel rightPanel;
    private JPanel masterConfigPanel;
    private JLabel cfgLblRemoteHost;
    private JLabel cfgLblRemotePort;
    private JPanel frameMonitorPanel;
    private JComboBox frameTypeComboBox;
    private JTextField frameInputText;
    private JButton frameSendBtn;
    private JButton monitorBtnStartDtV;
    private JButton monitorBtnTestFrC;
    private JButton monitorBtnStartDtC;
    private JButton monitorBtnStopDtV;
    private JButton monitorBtnStopDtC;
    private JButton monitorBtnTestFrV;
    private JButton monitorBtnTotalCall100;
    private JButton monitorBtnTotalCall101;

    private String[] monitorTcHeader = {"地址", "类型", "值", "状态", "更新时间"};
    private DefaultTableModel monitorTcTableModel = new DefaultTableModel(new Object[0][0], monitorTcHeader);
    private JTable monitorTcTable;
    private JLabel monitorTcCount;

    private String[] monitorTaHeader = {"地址", "类型", "值", "状态", "更新时间"};
    private DefaultTableModel monitorTaTableModel = new DefaultTableModel(new Object[0][0], monitorTaHeader);
    private JTable monitorTaTable;
    private JLabel monitorTaCount;

    private String[] monitorTmHeader = {"地址", "类型", "值", "有效性", "更新时间"};
    private DefaultTableModel monitorTmTableModel = new DefaultTableModel(new Object[0][0], monitorTmHeader);
    private JTable monitorTmTable;
    private JLabel monitorTmCount;

    private String[] monitorTsHeader = {"地址", "类型", "值", "有效性", "当前值", "被取代", "被闭锁", "更新时间"};
    private DefaultTableModel monitorTsTableModel = new DefaultTableModel(new Object[0][0], monitorTsHeader);
    private JTable monitorTsTable;
    private JLabel monitorTsCount;

    private JTextField monitorSendSeq;
    private JTextField monitorRecvSeq;
    private JPanel monitorTmPanel;
    private JScrollPane mainMonitorScrollPane;
    private JComboBox monitorControlTypeCbox;
    private JTextField monitorControlAddrText;
    private JComboBox monitorControlSwitchCbox;
    private JButton monitorControlSendBtn;
    private JComboBox monitorTaCbox;
    private JTextField monitorTaAddr;
    private JTextField monitorTaValue;
    private JButton monitorTaSendBtn;
    private JPanel mainMonitorPanel;

    private final Iec104MasterManager iec104Client;
    private final ScheduledExecutorService executor;
    private volatile DisplayingMasterInfo displayingMasterInfo;

    public MonitorPanel(Iec104MasterManager iec104Client, ScheduledExecutorService executor) {
        this.iec104Client = iec104Client;
        this.executor = executor;
        this.displayingMasterInfo = new DisplayingMasterInfo();
        setLayout(new BorderLayout());
        add(mainPanel, BorderLayout.CENTER);

        initMasterNameList();
        initRightTabbedPane();
        initExecutorTasks();
        initMainMonitorScrollPane();
        initMainMonitorTextArea();
        initMonitorBtns();
        initFrameSendBtn();
        initTmTable();
        initTsTable();
        initTcTable();
        initTaTable();
    }

    private void initMasterNameList() {
        masterNameList.setModel(masterNameListModel);
        iec104Client.addEventListener(new EventListener<ListenableContainerEvent<Master>>() {
            @Override
            public void onEvent(ListenableContainerEvent<Master> event) {
                if (event.getType() == ListenableContainerEvent.Type.ELEMENT_ADDED
                || event.getType() == ListenableContainerEvent.Type.ELEMENT_REMOVED) {
                    synchronized (masterNameListModel) {
                        masterNameListModel.clear();
                        iec104Client.forEachMaster(master -> masterNameListModel.addElement(new MasterLine(master)));
                    }
                }
            }
        });

        PduRecvCallback<Apdu> callback = null;
        masterNameList.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                final int index = masterNameList.getSelectedIndex();
                final MasterLine masterLine = masterNameListModel.getElementAt(index);
                displayingMasterInfo.changeTo(masterLine);
                refreshConfigData(masterLine.getMaster());
            }
        });
    }

    private void initRightTabbedPane() {
        rightTabbedPane.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                final int index = rightTabbedPane.getSelectedIndex();
                if (index == 0 && !displayingMasterInfo.isEmpty()) {
                    refreshConfigData(displayingMasterInfo.line.getMaster());
                }
            }
        });
    }

    private void initExecutorTasks() {
        executor.scheduleAtFixedRate(() -> {
            if (displayingMasterInfo.isEmpty()) return;

            final Master master = displayingMasterInfo.line.getMaster();
            final MDataInfo dataInfo = master.dataInfo();
            // 更新监测数据
            refreshMonitorData(master);
            // 更新遥控数据
            final List<MTcDataHandler.PtState> ptStates = dataInfo.listAllDoingTcPtStates();
            monitorTcTableModel.getDataVector().removeAllElements();
            SwingUtilities.updateComponentTreeUI(monitorTcTable);
            for (MTcDataHandler.PtState ptState : ptStates) {
                String[] data = {
                        String.valueOf(ptState.getAddr()),
                        ptState.getType() == Tc.Type.ONE_POINT ? "单点" : "双点",
                        ptState.isSwitchOn() ? "on" : "off",
                        ptState.getState().getDesc(),
                        DateFormatter.FORMATTER_1.format(new Date())
                };
                updateTableModel(monitorTcTableModel, ptState.getAddr(), data);
            }
            monitorTcCount.setText("数量：" + monitorTcTableModel.getRowCount());
            // 更新遥调数据
            final List<MTaDataHandler.PtState> taPtStates = dataInfo.listAllDoingTaPtStates();
            monitorTaTableModel.getDataVector().removeAllElements();
            SwingUtilities.updateComponentTreeUI(monitorTaTable);
            for (MTaDataHandler.PtState taPtState : taPtStates) {
                String[] data = {
                        String.valueOf(taPtState.getAddr()),
                        taPtState.valType().getDesc(),
                        taPtState.getVal().toString(),
                        taPtState.getState().getDesc(),
                        DateFormatter.FORMATTER_1.format(new Date())
                };
                updateTableModel(monitorTaTableModel, taPtState.getAddr(), data);
            }
            monitorTaCount.setText("数量：" + monitorTaTableModel.getRowCount());
        }, 0, 1, TimeUnit.SECONDS);
    }

    private void initMainMonitorScrollPane() {
        AtomicBoolean canKeepByMainMonitorTextArea = new AtomicBoolean(true);
        AtomicBoolean canKeepByMainMonitorScrollPane = new AtomicBoolean(true);
        mainMonitorScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
            public void adjustmentValueChanged(AdjustmentEvent e) {
                if (canKeepByMainMonitorTextArea.get() && canKeepByMainMonitorScrollPane.get()) {
                    e.getAdjustable().setValue(e.getAdjustable().getMaximum());
                }
            }
        });
        mainMonitorTextArea.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
                canKeepByMainMonitorTextArea.set(false);
            }

            @Override
            public void mouseExited(MouseEvent e) {
                canKeepByMainMonitorTextArea.set(true);
            }
        });
        mainMonitorScrollPane.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                canKeepByMainMonitorScrollPane.set(false);
            }

            @Override
            public void focusLost(FocusEvent e) {
                canKeepByMainMonitorScrollPane.set(true);
            }
        });
    }

    private void initMainMonitorTextArea() {
    }

    private void initMonitorBtns() {
        monitorBtnTotalCall100.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                final Master master = displayingMasterInfo.line.getMaster();
                master.getIAsduSender().sendTotalCall100();
            }
        });
    }

    private void initFrameSendBtn() {
    }

    private void initTmTable() {
        monitorTmTable.setModel(monitorTmTableModel);
    }

    private void initTsTable() {
        monitorTsTable.setModel(monitorTsTableModel);
    }

    private void initTcTable() {
        monitorControlSendBtn.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                final String itemStr = monitorControlTypeCbox.getSelectedItem().toString();
                boolean isOnePoint = itemStr.equals("单点");
                Integer addr = Integer.valueOf(monitorControlAddrText.getText());
                boolean isSwitchOn = monitorControlSwitchCbox.getSelectedItem().toString().equals("on");
                final Master master = displayingMasterInfo.line.getMaster();
                if (isOnePoint) {
                    master.getIAsduSender().sendOnePointTcSelect(addr, isSwitchOn).addListener(future -> {
                        if (future.isSuccess()) {
                            master.getIAsduSender().sendOnePointTcExecute(addr, isSwitchOn);
                        }
                    });
                } else {
                    displayingMasterInfo.line.getMaster().getIAsduSender().sendTwoPointTcSelect(addr, isSwitchOn).addListener(future -> {
                        if (future.isSuccess()) {
                            master.getIAsduSender().sendTwoPointTcExecute(addr, isSwitchOn);
                        }
                    });
                }
            }
        });
        monitorTcTable.setModel(monitorTcTableModel);
    }

    private void initTaTable() {
        monitorTaSendBtn.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                final String valType = monitorTaCbox.getSelectedItem().toString();
                Integer addr = Integer.valueOf(monitorTaAddr.getText());
                final Master master = displayingMasterInfo.line.getMaster();
                switch (valType) {
                    case "短浮点数":
                        Float val1 = Float.valueOf(monitorTaValue.getText());
                        master.getIAsduSender().sendTaSelectFloat(addr, val1).addListener(new MasterFutureListener<SendTaSelectRes>() {
                            @Override
                            public void operationComplete(MasterFuture<SendTaSelectRes> future) {
                                if (future.isSuccess()) {
                                    master.getIAsduSender().sendTaExecuteFloat(addr, val1);
                                }
                            }
                        });
                        break;
                    case "归一化值":
                        Integer val2 = Integer.valueOf(monitorTaValue.getText());
                        break;
                    case "标度化值":
                        Integer val3 = Integer.valueOf(monitorTaValue.getText());
                        break;
                }
            }
        });
        monitorTaTable.setModel(monitorTaTableModel);
    }

    private void refreshConfigData(Master master) {
        cfgValRemoteHost.setText(master.remoteHost());
        cfgValRemotePort.setText(String.valueOf(master.remotePort()));
        cfgValRtuAddr.setText(String.valueOf(master.localRtuAddr()));
        cfgValAutoStartDt.setText(master.controlInfo().isAutoStartDtV() ? "是" : "否");
        cfgValTotalCall100PeriodSeconds.setText(String.valueOf(master.dataInfo().totalCall100PeriodSeconds()));
        cfgValTotalCall101PeriodSeconds.setText(String.valueOf(master.dataInfo().totalCall101PeriodSeconds()));
        cfgValT0.setText(String.valueOf(master.controlInfo().t0()));
        cfgValT1.setText(String.valueOf(master.controlInfo().t1()));
        cfgValT2.setText(String.valueOf(master.controlInfo().t2()));
        cfgValT3.setText(String.valueOf(master.controlInfo().t3()));
    }

    private void refreshMonitorData(Master master) {
        MControlInfo controlInfo = master.controlInfo();
        monitorIsStartedDt.setText(controlInfo.isStartedDt() ? "已激活" : "未激活");
        monitorIsInitEnd.setText(controlInfo.isInitCompleted() ? "已结束" : "未结束");
        monitorIsTestFr.setText(controlInfo.isTestingFr() ? "正在测试" : "空闲");
        monitorIsTotaCall100.setText(controlInfo.isDoingTotalCall100() ? "正在执行" : "空闲");
        monitorIsTotalCall101.setText(controlInfo.isDoingTotalCall101() ? "正在执行" : "空闲");
        monitorSendSeq.setText(String.valueOf(controlInfo.sendSeq()));
        monitorRecvSeq.setText(String.valueOf(controlInfo.recvSeq()));
    }

    private static class MasterLine {
        private Master master;

        private MasterLine(Master master) {
            this.master = master;
        }

        public Master getMaster() {
            return master;
        }

        @Override
        public String toString() {
            return master.remoteHost() + ": " + master.remotePort();
        }
    }

    private static void updateTableModel(DefaultTableModel tableModel, int addr, String[] data) {
        synchronized (tableModel) {
            // 检查是否已存在该地址信息
            boolean hasAddr = false;
            int row = 0;
            for (int i = 0; i < tableModel.getRowCount(); i++) {
                final Integer inAddr = Integer.valueOf((String) tableModel.getValueAt(i, 0));
                if (inAddr.equals(addr)) {
                    hasAddr = true;
                    row = i;
                    break;
                }
            }
            if (hasAddr) {
                for (int i = 0; i < data.length; i++) {
                    tableModel.setValueAt(data[i], row, i);
                }
            } else {
                int insertRow = 0;
                for (int i = 0; i < tableModel.getRowCount(); i++) {
                    final Integer inAddr = Integer.valueOf((String) tableModel.getValueAt(i, 0));
                    if (inAddr < addr) {
                        continue;
                    }
                    insertRow = i;
                }
                if (insertRow == 0) insertRow = tableModel.getRowCount();
                tableModel.insertRow(insertRow, data);
            }
        }
    }

    private class DisplayingMasterInfo {
        private MasterLine line;
        private List<PduRecvCallback<Apdu>> frameRecvCallbacks = new ArrayList<>();
        private List<PduSendCallback<Apdu>> frameSendCallbacks = new ArrayList<>();

        public DisplayingMasterInfo() {
            initSendCallbacks();
            initRecvCallbacks();
        }

        private void initSendCallbacks() {
            // 更新文本域
            frameSendCallbacks.add(new PduSendCallback<Apdu>() {
                @Override
                public void beforeSend(Apdu pdu) {
                    appendApduInfoToTextArea(pdu, true);
                }

                @Override
                public void afterSendSuccess(Apdu pdu) {
                }
            });
        }

        private void initRecvCallbacks() {
            // 更新文本域
            frameRecvCallbacks.add(new PduRecvCallback<Apdu>() {
                @Override
                public boolean whenRecv(Apdu apdu) {
                    appendApduInfoToTextArea(apdu, false);
                    return false;
                }
            });
            // 更新表格
            frameRecvCallbacks.add(new PduRecvCallback<Apdu>() {
                @Override
                public boolean whenRecv(Apdu frame) {
                    if (!frame.isIType()) return false;
                    final IApdu iApdu = frame.castToIType();
                    final IAsdu iAsdu = iApdu.getIAsdu();
                    final TypeTag typeTag = iAsdu.getTypeTag();

                    for (InfoObj infoObj : iAsdu.getInfoObjs()) {
                        final int addr = infoObj.getAddr();
                        final InfoElem infoElem = infoObj.getInfoElem();
                        final Time2a time2a = infoObj.getTime2a();
                        final String time2aStr = DateFormatter.FORMATTER_1.format(time2a != null ? time2a.toDate() : new Date());
                        final Qua qua = infoObj.getQua();

                        // 如果是遥测
                        if (infoElem.isTmInfoElem()) {
                            final TmInfoElem tmInfoElem = infoElem.castToTmInfoElem();
                            final TmQua tmQua = qua.castToTmQua();
                            String[] data = {
                                    String.valueOf(addr),
                                    tmInfoElem.getTmType().getDesc(),
                                    tmInfoElem.getNumberVal().toString(),
                                    tmQua.getValid().getDesc(),
                                    time2aStr
                            };

                            updateTableModel(monitorTmTableModel, addr, data);
                        } else if (infoElem.isTsInfoElem()) {
                            final TsInfoElem tsInfoElem = infoElem.castToTsInfoElem();
                            String[] data = {
                                    String.valueOf(addr),
                                    typeTag.isOnePointTs() ? "单点" : "双点",
                                    tsInfoElem.isTsSwitchOn() ? "on" : "off",
                                    tsInfoElem.isTsValid() ? "有效" : "无效",
                                    tsInfoElem.isTsCurrVal() ? "是" : "否",
                                    tsInfoElem.isTsReplaced() ? "是" : "否",
                                    tsInfoElem.isTsLocked() ? "是" : "否",
                                    time2aStr
                            };
                            updateTableModel(monitorTsTableModel, addr, data);
                        }
                    }
                    monitorTmCount.setText("数量：" + monitorTmTableModel.getRowCount());
                    monitorTsCount.setText("数量：" + monitorTsTableModel.getRowCount());
                    return false;
                }
            });
        }

        private void appendApduInfoToTextArea(Apdu apdu, boolean isSend) {
            String flagStr = "";
            if (isSend) {
                flagStr = "发送";
            } else {
                flagStr = "接收";
            }
            synchronized (mainMonitorTextArea) {
                mainMonitorTextArea.append(DateFormatter.FORMATTER_1.format(new Date()));
                mainMonitorTextArea.append("\n");
                mainMonitorTextArea.append(flagStr + " " + apdu.apduType().toString());
                switch (apdu.apduType()) {
                    case UType:
                        final UApdu uApdu = apdu.castToUType();
                        mainMonitorTextArea.append("，控制类型：" + uApdu.getUApci().getCtrlArea().getUCtrlType().getDesc());
                        break;
                    case SType:
                        final SApdu sApdu = apdu.castToSType();
                        mainMonitorTextArea.append("，接收序号：" + sApdu.getSApci().getCtrlArea().getRecvSeq());
                        break;
                    case IType:
                        final IApdu iApdu = apdu.castToIType();
                        final ICtrlArea ctrlArea = iApdu.getIApci().getCtrlArea();
                        mainMonitorTextArea.append("，发送序号：" + ctrlArea.getSendSeq() + "，接收序号：" + ctrlArea.getRecvSeq());
                        break;
                }
                mainMonitorTextArea.append("\n");
                mainMonitorTextArea.append(apdu.toString());
                mainMonitorTextArea.append("\n\n");
            }
        }

        public boolean isEmpty() {
            return line == null;
        }

        public void changeTo(MasterLine masterLine) {
            if (line != null) {
//                line.getMaster().getIAsduSender().removeSendCallbacks(frameSendCallbacks);
//                line.getMaster().getIAsduRecver().removeRecvCallbacks(frameRecvCallbacks);
            }
            this.line = masterLine;
            clearComponentsContent();
//            line.getMaster().getIAsduRecver().addRecvCallbacks(frameRecvCallbacks);
//            line.getMaster().getIAsduSender().addSendCallbacks(frameSendCallbacks);
        }

        private void clearComponentsContent() {
            mainMonitorTextArea.setText("");
            monitorTmTableModel.getDataVector().removeAllElements();
            SwingUtilities.updateComponentTreeUI(monitorTmTable);
            monitorTsTableModel.getDataVector().removeAllElements();
            SwingUtilities.updateComponentTreeUI(monitorTsTable);
        }
    }

    private enum TabbedType {
        CONFIG,
        MONITOR
    }
}
